package jumpingnotes.red5;

import java.io.IOException;
import java.io.RandomAccessFile;

import org.red5.server.api.stream.IStreamFilenameGenerator;
import org.red5.server.api.stream.ResourceExistException;
import org.red5.server.api.stream.ResourceNotFoundException;
import org.red5.server.api.stream.IStreamFilenameGenerator.GenerationType;
import org.red5.server.stream.ClientBroadcastStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JNClientBroadcastStream extends ClientBroadcastStream {
	private static final Logger log = LoggerFactory.getLogger(JNClientBroadcastStream.class);

	@Override
	public void saveAs(String name, boolean isAppend) throws IOException,
			ResourceNotFoundException, ResourceExistException {
		if (isAppend) {
			String newName = preprocess(name);
			if (newName != null) {
				name = newName;
			}
		}
		super.saveAs(name, isAppend);
	}

	/**
	 * The FLV name to append follows "flvname#ts.flv" where
	 * ".flv" is optional. "ts" is the millisecond value the
	 * stream is to be appended to the file. The method will
	 * search the FLV file for the point that has the smallest
	 * timestamp that is bigger than the "ts" provided.
	 * 
	 * @param name The FLV name that follows "flvname#ts.flv".
	 * @return The name of FLV processed or <tt>null</tt> if the
	 * file is not processed at all.
	 * @throws IOException
	 */
	protected String preprocess(String name) throws IOException {
		int tsIdx = name.lastIndexOf("#");
		if (tsIdx < 0) {
			return null;
		}
		String tsStr = name.substring(tsIdx+1);
		int suffixIdx = tsStr.lastIndexOf(".");
		String suffix = null;
		if (suffixIdx >= 0) {
			suffix = tsStr.substring(suffixIdx+1);
			tsStr = tsStr.substring(0, suffixIdx);
		}
		name = name.substring(0, tsIdx);
		if (suffix != null) {
			if ("flv".equals(suffix)) {
				name += "." + suffix;
			} else {
				return null;
			}
		}
		int ts = 0;
		try {
			ts = Integer.parseInt(tsStr);
		} catch (NumberFormatException e) {
			return null;
		}
		if (ts < 0) {
			return null;
		}
		IStreamFilenameGenerator filenameGenerator =
			(IStreamFilenameGenerator) getScope().getContext().getBean(IStreamFilenameGenerator.BEAN_NAME);
		String flvFileName = filenameGenerator.generateFilename(getScope(), name, GenerationType.RECORD);
		RandomAccessFile flv = null;
		try {
			flv = new RandomAccessFile(flvFileName, "rw");
			long flvLength = flv.length();
			long currentTagSize = 0;
			long currentTagPos = flvLength;
			long lastTagPos = flvLength;
			if (flvLength < 13) {
				return null;
			}
			do {
				flv.seek(currentTagPos - 4);
				currentTagSize = readLong(flv);
				lastTagPos = currentTagPos;
				currentTagPos -= currentTagSize + 4;
				if (currentTagSize == 0) {
					// reach the beginning
					break;
				} else if (currentTagSize < 0) {
					// something wrong it seems...
					return null;
				}
				flv.seek(currentTagPos + 4);
				int currentTS = readInt3(flv);
				if (currentTS <= ts) {
					// lastTagPos points the cut position
					break;
				}
			} while (currentTagPos >= 13);
			flv.setLength(lastTagPos);
		} finally {
			if (flv != null) {
				flv.close();
			}
		}
		return name;
	}
	
	private long readLong(RandomAccessFile raf) throws IOException {
		byte[] buf = new byte[4];
		raf.read(buf);
		long result = 0;
		result += (buf[0] & 0x0ff) << 24;
		result += (buf[1] & 0x0ff) << 16;
		result += (buf[2] & 0x0ff) << 8;
		result += buf[3] & 0x0ff;
		return result;
	}
	
	private int readInt3(RandomAccessFile raf) throws IOException{
		byte[] buf = new byte[3];
		raf.read(buf);
		int result = 0;
		result += (buf[0] & 0x0ff) << 16;
		result += (buf[1] & 0x0ff) << 8;
		result += buf[2] & 0x0ff;
		return result;
	}
}
